﻿using System.Diagnostics;
using System.Text.RegularExpressions;
using AutoMapper;
using HelenServer.BugEngine.Contracts;
using HelenServer.Grpc.Contracts;
using HelenServer.Migrator;
using Microsoft.EntityFrameworkCore;

namespace HelenServer.BugEngine.Dal;

[ServiceProvider(typeof(IDalMigrateService), SqlServerConstants.ProviderName)]
public class DalMigrateService : IDalMigrateService
{
    private readonly IRepo<BugEngineDbContext> _repo;
    private readonly IGrpcClientFactory _factory;
    private readonly IMapper _mapper;
    private readonly ILogger<DalMigrateService> _logger;
    private readonly ILoggerFactory _loggerFactory;

    private readonly Regex _editorLinkRegexer = new(@"(bug-view-(\d+))");
    private readonly Regex _editorImageRegexer = new(@"({(\d+))");

    private FileStorageMigrater _fileStorageMigrater = null!;
    private readonly string _zentaoUploadPrefix = @"/zentao/data/upload/1/";

    public DalMigrateService(
        BugEngineDbContext context,
        IGrpcClientFactory factory,
        IMapper mapper,
        ILogger<DalMigrateService> logger,
        ILoggerFactory loggerFactory
     )
    {
        _repo = new Repo<BugEngineDbContext>(context);
        _factory = factory;
        _mapper = mapper;
        _logger = logger;
        _loggerFactory = loggerFactory;
    }

    public async Task<OperationResult<bool>> MigrateAsync(CancellationToken cancellationToken = default)
    {
        var ztContext = MigrateProvider.Context ?? throw new InvalidOperationException("请先调用MigrateProvider.ReadyToMigrate方法");

        _fileStorageMigrater = new(
            _factory.GetService<IFSMigrateQueue, IEnumerable<KeyValuePair<int, IEnumerable<NotifySpiderGrpcModel>>>, GrpcResult<bool>>("fs"),
            _factory.GetService<IFSMigrateSpider, int, GrpcResult<IReadOnlyDictionary<int, int[]>>>("fs"),
            new Metadata
            {
                {"zt_authorization", Helen.Metadata["zt_authorization"] },
            },
            _loggerFactory);

        try
        {/*
            #region Custom Extra            
            var bugTypeEntities = MigrateCustomFieldHandler.Map<BugType>();
            await _repo.Context.BugTypes.AddRangeAsync(bugTypeEntities, cancellationToken);

            var solutionEntities = MigrateCustomFieldHandler.Map<Solution>();
            await _repo.Context.Solutions.AddRangeAsync(solutionEntities, cancellationToken);

            var osEntities = MigrateCustomFieldHandler.Map<Os>();
            await _repo.Context.Os.AddRangeAsync(osEntities, cancellationToken);

            var browserEntities = MigrateCustomFieldHandler.Map<Browser>();
            await _repo.Context.Browsers.AddRangeAsync(browserEntities, cancellationToken);

            await _repo.Context.SaveChangesAsync(cancellationToken);
            #endregion

            #region Product
            var productEntities = _mapper.Map<IEnumerable<ZtProduct>, IEnumerable<Product>>(MigrateProvider.DbCache.ZtProducts);

            await _repo.Context.AddIdentityRangeAsync(productEntities, cancellationToken);
            #endregion

            #region Module            
            var moduleEntities = _mapper.Map<IEnumerable<ZtModule>, IEnumerable<Module>>(MigrateProvider.DbCache.ZtModules);

            await _repo.Context.AddIdentityRangeAsync(moduleEntities, cancellationToken);
            #endregion

            #region Release            
            var releaseEntities = _mapper.Map<IEnumerable<ZtRelease>, IEnumerable<Release>>(MigrateProvider.DbCache.ZtReleases);

            await _repo.Context.AddIdentityRangeAsync(releaseEntities, cancellationToken);
            #endregion
            */
            #region OperationLog            
            var joinResult =
                (from za in MigrateProvider.DbCache.ZtActions
                 join zh in MigrateProvider.DbCache.ZtHistory on za.Id equals zh.Action into zJoin
                 from zj in zJoin.DefaultIfEmpty()
                 where za.ObjectType == "bug"
                 select new KeyValuePair<uint, ZtHistory>(za.ObjectId, zj)).ToList();

            // bugId -> 所有的历史记录
            var logMaps = joinResult.GroupBy(
                n => n.Key,
                (k, v) =>
                   new KeyValuePair<uint, List<ZtHistory>>(k, v.Where(x => x.Key == k).Select(y => y.Value).ToList()))
                .ToDictionary(k => k.Key, v => v.Value);

            var watch = new Stopwatch();

            watch.Restart();
            var logEntitiesMap = logMaps.Keys.Select(n =>
            {
                var actions = MigrateProvider.DbCache.ZtActions.Where(x => x.ObjectId == n && x.ObjectType == "bug");
                var logs = _mapper.Map<IEnumerable<ZtAction>, IEnumerable<OperationLog>>(actions);

                logs.ForEach(m =>
                {
                    m.Details = _mapper.Map<IEnumerable<ZtHistory>, IEnumerable<OperationLogDetail>>(logMaps[n].Where(n => n != null && n.Action == m.Id)).ToArray();
                });

                return new KeyValuePair<int, IEnumerable<OperationLog>>((int)n, logs);
            });
            watch.Stop();

            var t2 = watch.ElapsedMilliseconds;
            #endregion

            #region Bug            
            var bugsEntities = _mapper.Map<ZtBug[], Bug[]>(MigrateProvider.DbCache.ZtBugs);
            var bugTempMaps = bugsEntities.Select(n => new KeyValuePair<int, Bug>(n.Id, n)).ToDictionary(k => k.Key, v => v.Value);
            // 9s
            foreach (var log in logEntitiesMap)
            {
                bugTempMaps[log.Key].OperationLogs = log.Value.ToArray();
            }

            bugTempMaps.Clear();
            bugTempMaps = null;

            #region 处理Editor中的Bug链接
            //var links = bugsEntities.Where(n => n.Steps.Contains("/zentao/bug-view-"));
            // todo
            #endregion

            #region 处理附件 
            //var bugsEntities = _mapper.Map<ZtBug[], Bug[]>(MigrateProvider.DbCache.ZtBugs);
            //var watch = new Stopwatch();

            var zentaoFileServerUri = $"http://{Helen.Metadata["zt_ip"]}:{Helen.Metadata["zt_port"]}{_zentaoUploadPrefix}";

            var logImagesDict = MigrateProvider.DbCache.ZtActions
                .Where(n => n.Comment.Contains("src=\"{"))
                .GroupBy(n => n.ObjectId, (k, v) => new KeyValuePair<int, ZtAction[]>((int)k, v.Where(x => x.ObjectId == k).ToArray()))
                .Select(n => new KeyValuePair<int, int[]>(n.Key, n.Value.SelectMany(x => this.FileIdsParse(x.Comment)).ToArray()))
                .Select(n => new KeyValuePair<int, NotifySpiderGrpcModel[]>(n.Key, ConvertToNotifySpiderGrpcModels(n.Value)));

            #region Local Function            
            NotifySpiderGrpcModel[] ConvertToNotifySpiderGrpcModels(int[] ids)
            {
                var actual = ids.Select(n =>
                {
                    var ztFileObject = MigrateProvider.DbCache.ZtFiles.ToList().Find(x => x.Id == (uint)n);

                    return ztFileObject is null
                        ? new NotifySpiderGrpcModel { AddedBy = -1 }
                        : new NotifySpiderGrpcModel
                        {
                            Name = ztFileObject.Title,
                            Extension = ztFileObject.Extension,
                            Url = $"{zentaoFileServerUri}{ztFileObject.Pathname.Split('.')[0]}",
                            AddedBy = (int)MigrateProvider.DbCache.ZtUsers!.First(n => n.Account == ztFileObject.AddedBy).Id
                        };
                }).ToList();

                actual.RemoveAll(n => n.AddedBy == -1);

                return actual.ToArray();
            }
            #endregion

            var editorImagesDict = bugsEntities
                .Where(n => n.Steps.Contains("src=\"{"))
                .Select(n => new KeyValuePair<int, int[]>(n.Id, this.FileIdsParse(n.Steps)))
                .Select(n => new KeyValuePair<int, NotifySpiderGrpcModel[]>(n.Key, ConvertToNotifySpiderGrpcModels(n.Value)));

            // Spider Map
            var editorFilesMap = new Dictionary<int, NotifySpiderGrpcModel[]>(editorImagesDict);
            var logFilesMap = new Dictionary<int, NotifySpiderGrpcModel[]>(logImagesDict);
            var attchmentFilesMap = new Dictionary<int, NotifySpiderGrpcModel[]>();

            var ztAttachments = MigrateProvider.DbCache.ZtFiles.Where(n => n.ObjectType == "bug" && string.IsNullOrEmpty(n.Extra));
            var attachmentBugIds = ztAttachments.DistinctBy(n => n.ObjectId).Select(n => n.ObjectId);

            foreach (var item in attachmentBugIds)
            {
                var fileIds = ztAttachments.Where(n => n.ObjectId == item).Select(x => (int)x.Id).ToArray();

                attchmentFilesMap.Add(item, ConvertToNotifySpiderGrpcModels(fileIds));
            }

            var editfilesCount = editorFilesMap.Values.ToList().ComputeCount();
            var attachmentfilesCount = attchmentFilesMap.Values.ToList().ComputeCount();
            var logfilesCount = logFilesMap.Values.ToList().ComputeCount();

            watch.Restart();
            var editorFilesResult = await _fileStorageMigrater.MigrateAsync(editorFilesMap, cancellationToken);
            watch.Stop();

            var te = watch.ElapsedMilliseconds;

            watch.Restart();
            var attachmentsResult = await _fileStorageMigrater.MigrateAsync(attchmentFilesMap, cancellationToken);
            watch.Stop();

            var ta = watch.ElapsedMilliseconds;

            watch.Restart();
            var logFilesResult = await _fileStorageMigrater.MigrateAsync(logFilesMap, cancellationToken);
            watch.Stop();

            var tl = watch.ElapsedMilliseconds;

            watch.Restart();
            editorFilesResult.ForEach(x =>
            {
                var target = bugsEntities.First(n => n.Id == x.Key);

                if (target is not null)
                {
                    target.EditorFiles = x.Value;
                }
            });

            attachmentsResult.ForEach(x =>
            {
                var target = bugsEntities.First(n => n.Id == x.Key);

                if (target is not null)
                {
                    target.Attachments = x.Value;
                }
            });

            logFilesResult.ForEach(x =>
            {
                var target = bugsEntities.First(n => n.Id == x.Key);

                if (target is not null)
                {
                    target.LogFiles = x.Value;
                }
            });
            watch.Stop();

            var tc = watch.ElapsedMilliseconds;
            #endregion

            await _repo.Context.AddIdentityRangeAsync(bugsEntities, cancellationToken);

            #endregion
        }
        catch (Exception ex)
        {
            return OperationResult<bool>.Failed(ex.Message);
        }

        await BugEngineMigrateProfile.DisposeAsync();

        return OperationResult<bool>.OK(true);
    }



    private int[] LinkIdsParse(string value)
    {
        return _editorLinkRegexer.Matches(value).Select(n => int.Parse(n.Value[9..])).Distinct().ToArray();
    }

    private int[] FileIdsParse(string value)
    {
        return _editorImageRegexer.Matches(value).Select(n => int.Parse(n.Value[1..])).Distinct().ToArray();
    }
}